//
//	File: OyDirect3D9_Camera.cpp
//
#include "OyDirect3D9.h"
#include "Camera/D3D9Camera.h"

/**
 * Set current camera's viewmatrix.
 * -> IN: float* - 4 float building camera's right vector
 *        float* - 4 float building camera's up vector
 *        float* - 4 float building camera's direction vector
 *        float* - 4 float building camera's pos vector
 */
HRESULT	OyDirect3D9::SetViewMatrix3D(const OyVector4D& vcRight, const OyVector4D& vcUp, 
								const OyVector4D& vcDir, const OyVector4D& vcEyePos)
{
   if( !m_bRunning || !m_pCurrentCamera )
	   return OY_FAIL;

   m_pCurrentCamera->SetViewMatrix3D(vcRight, vcUp, vcDir, vcEyePos);

   return OY_OK;
}

/**
 * Calculates current camera View matrix for given point and calls SetViewMatrix3D
 * to active the look at matrix.
 * -> IN: float* - 4 floats building camera's position vector
 *        float* - 4 floats building lookat point vector
 *        float* - 4 floats building world up vector at cam pos
 */
HRESULT	OyDirect3D9::SetViewMatrixLookAt(const OyVector4D& vcPos, const OyVector4D& vcPoint, 
									const OyVector4D& vcWorldUp)
{
	OyVector4D vcDir, vcTemp, vcUp;

	vcDir = vcPoint - vcPos;
	vcDir.Normalize();

	// calculate up vector
	float fDot = vcWorldUp * vcDir;
 	vcTemp = vcDir * fDot;
	vcUp = vcWorldUp - vcTemp;
	float fL = vcUp.GetLength();

	// if length too small take normal y axis as up vector
	if(fL < 1e-6f) 
	{
		OyVector4D vcY;
		vcY.Set(0.0f, 1.0f, 0.0f);

		vcTemp = vcDir * vcDir.y;
		vcUp = vcY - vcTemp;

		fL = vcUp.GetLength();

		// if still too small take z axis as up vector
		if (fL < 1e-6f) 
		{
			vcY.Set(0.0f, 0.0f, 1.0f);

			vcTemp = vcDir * vcDir.z;
			vcUp = vcY - vcTemp;

			// if still too small we are lost         
			fL = vcUp.GetLength();
			if(fL < 1e-6f) 
				return OY_FAIL;
		}
	}

	vcUp /= fL;

	// build right vector using cross product
	OyVector4D vcRight;
	vcRight.Cross(vcUp, vcDir);
	vcRight.Normalize();

	vcUp.Cross(vcDir, vcRight);

	return SetViewMatrix3D(vcRight, vcUp, vcDir, vcPos);
}

void OyDirect3D9::SetClippingPlanes(float fNear, float fFar)
{
	if( m_pCurrentCamera )
		m_pCurrentCamera->SetClippingPlanes( fNear, fFar );
}

HRESULT	OyDirect3D9::GetFrustrum(OyPlane* pPlane)
{
	if( m_pCurrentCamera )
	{
		m_pCurrentCamera->GetFrustrum(pPlane);
		return OY_OK;
	}
	return OY_FAIL;
}

void OyDirect3D9::GetViewMatrix(float *pMat)
{
	if( m_pCurrentCamera )
		m_pCurrentCamera->GetViewMatrix(pMat);
}

void OyDirect3D9::GetProjMatrix(float *pMat)
{
	if( m_pCurrentCamera )
		m_pCurrentCamera->GetProjMatrix(pMat);
}

/**
 * Calculate perspective and orthogonal projection matrix for current camera
 * using given values.
 * -> IN: float       - field of view (horizontal)
 *        float		  - aspect ration (viewport height / width)
 */
HRESULT	OyDirect3D9::SetProjMatrixParams(float fFOV, float fAspect)
{
	if(!m_pCurrentCamera)
		return OY_FAIL;

	if( m_pCurrentCamera->m_Mode == EMD_PERSPECTIVE )
	{
		// PERSPECTIVE PROJEKTION MATRIX
		if( FAILED(m_pCurrentCamera->CalcPerspProjMatrix(fFOV, fAspect)) )
			return OY_FAIL;
	}
	else if( m_pCurrentCamera->m_Mode == EMD_ORTHOGONAL )
	{
		OY_VIEWPORT vp = m_pCurrentCamera->m_ViewPort;
		if( FAILED(m_pCurrentCamera->CalcOrthoProjMatrix(
										(float)vp.X, (float)vp.X+vp.Width,
										(float)vp.Y, (float)vp.Y+vp.Height,
										m_pCurrentCamera->m_fNear,
										m_pCurrentCamera->m_fFar)) )
			return OY_FAIL;
	}	

	return OY_OK;
}

HRESULT OyDirect3D9::SetMode(OY_CAMERAMODE Mode)
{
	if(!m_bRunning || !m_pCurrentCamera) 
		return OY_FAIL;

	if(m_pCurrentCamera->m_Mode != Mode)
		m_pCurrentCamera->m_Mode = Mode;

   return OY_OK;
}

HRESULT	OyDirect3D9::SetViewPort(OY_VIEWPORT* pViewPort)
{
	if(!m_pCurrentCamera)
		return OY_FAIL;

	if( !pViewPort || (m_pCurrentCamera->m_Mode == EMD_TWOD) )
	{
		OY_VIEWPORT vpOwn;
		vpOwn.Width = m_dwWidth;
		vpOwn.Height = m_dwHeight;
		memcpy(&m_pCurrentCamera->m_ViewPort, &vpOwn, sizeof(OY_VIEWPORT));
	}
	else
		memcpy(&m_pCurrentCamera->m_ViewPort, pViewPort, sizeof(OY_VIEWPORT));

	D3DVIEWPORT9 pt;
	pt.X = m_pCurrentCamera->m_ViewPort.X;
	pt.Y = m_pCurrentCamera->m_ViewPort.Y;
	pt.Width = m_pCurrentCamera->m_ViewPort.Width;
	pt.Height = m_pCurrentCamera->m_ViewPort.Height;
	pt.MinZ = 0.0f;
	pt.MaxZ = 1.0f;
	m_pDevice->SetViewport(&pt);

	return OY_OK;
}

HRESULT	OyDirect3D9::GetViewPort(OY_VIEWPORT* pViewPort)
{
	if(!m_pCurrentCamera || !pViewPort)
		return OY_FAIL;
	pViewPort->X = m_pCurrentCamera->m_ViewPort.X;
	pViewPort->Y = m_pCurrentCamera->m_ViewPort.Y;
	pViewPort->Width = m_pCurrentCamera->m_ViewPort.Width;
	pViewPort->Height = m_pCurrentCamera->m_ViewPort.Height;
	return OY_OK;
}

/**
 * Cast a world ray from a given position on screen.
 * -> IN:  POINT       - point in screen coordinates
 * -> OUT: OyVector4D* - to fill with 4 floats for ray origin vextor
 *         OyVector4D* - to fill with 4 floats for ray direction
 */
void OyDirect3D9::Transform2Dto3D(const POINT &pt, OyVector4D *vcOrig, OyVector4D *vcDir)
{
	OyMatrix *pView = NULL;
	OyMatrix *pProj = NULL;
	OyMatrix mInvView;
	OyVector4D vcS;
	DWORD     dwWidth, dwHeight;

	if( m_pCurrentCamera )
	{
		dwWidth  = m_pCurrentCamera->m_ViewPort.Width;
		dwHeight = m_pCurrentCamera->m_ViewPort.Height;

		pView = &m_pCurrentCamera->m_mViewMatrix;
		pProj = &m_pCurrentCamera->m_mProjMatrix;

		// resize to viewportspace [-1,1] -> projection
		vcS.x =  ( ((pt.x*2.0f) / dwWidth) -1.0f) / pProj->_11;
		vcS.y = -( ((pt.y*2.0f) / dwHeight)-1.0f) / pProj->_22;
		vcS.z = 1.0f;

		// invert view matrix
		mInvView.InverseOf(*pView);

		// ray from screen to worldspace
		(*vcDir).x = (vcS.x * mInvView._11)	+ (vcS.y * mInvView._21) + (vcS.z * mInvView._31);
		(*vcDir).y = (vcS.x * mInvView._12)	+ (vcS.y * mInvView._22) + (vcS.z * mInvView._32);
		(*vcDir).z = (vcS.x * mInvView._13)	+ (vcS.y * mInvView._23) + (vcS.z * mInvView._33);
   
		// inverse translation.
		(*vcOrig).x = mInvView._41;
		(*vcOrig).y = mInvView._42;
		(*vcOrig).z = mInvView._43;

		// normalize
		(*vcDir).Normalize();
	}
}

/**
 * Cast given point from world space to screen space.
 * -> IN:     OyVector4D - vector to position in 3d space
 * -> RETURN: POINT      - corresponding point in 2d screen space
 */
POINT OyDirect3D9::Transform3Dto2D(const OyVector4D &vcPoint)
{
	POINT pt;
	pt.x = pt.y = 0;
	float fClip_x, fClip_y;
	float fXp, fYp, fWp;
	DWORD dwWidth, dwHeight;

	if( m_pCurrentCamera )
	{
		dwWidth  = m_pCurrentCamera->m_ViewPort.Width;
		dwHeight = m_pCurrentCamera->m_ViewPort.Height;

		fClip_x = (float)(dwWidth  >> 1);
		fClip_y = (float)(dwHeight >> 1);

		fXp =	(m_pCurrentCamera->m_mViewProjMatrix._11*vcPoint.x) +
				(m_pCurrentCamera->m_mViewProjMatrix._21*vcPoint.y) +
				(m_pCurrentCamera->m_mViewProjMatrix._31*vcPoint.z) + 
				(m_pCurrentCamera->m_mViewProjMatrix._41);
		fYp =	(m_pCurrentCamera->m_mViewProjMatrix._12*vcPoint.x) +
				(m_pCurrentCamera->m_mViewProjMatrix._22*vcPoint.y) +
				(m_pCurrentCamera->m_mViewProjMatrix._32*vcPoint.z) +
				(m_pCurrentCamera->m_mViewProjMatrix._42);
		fWp =	(m_pCurrentCamera->m_mViewProjMatrix._14*vcPoint.x) +
				(m_pCurrentCamera->m_mViewProjMatrix._24*vcPoint.y) +
				(m_pCurrentCamera->m_mViewProjMatrix._34*vcPoint.z) + 
				(m_pCurrentCamera->m_mViewProjMatrix._44);

		float fWpInv = 1.0f / fWp;

		// transform from [-1,1] to actual viewport dimensions
		pt.x = (LONG)( (1.0f + (fXp * fWpInv)) * fClip_x );
		pt.y = (LONG)( (1.0f + (fYp * fWpInv)) * fClip_y );
	}

	return pt;
}